# -*- coding: utf-8 -*-

import lts

__all__ =	[
	"show_nondiag_info",
	"share_all_events",
	"check_sync_type",
	"path_to_string",
	"tp_to_sys_path",
	"stack_to_path",
	"share_events",
	"SYNC_PARTIAL",
	"SYNC_EMPTY",
	"SYNC_FULL",
	"str_time",
	"graph"
]

# Tipos de sincronizacion.
SYNC_EMPTY		=	0
SYNC_PARTIAL	=	1
SYNC_FULL		=	2

def str_time (t):
	"""
		Descripcion:
			Convierte el arguemtno "t", dado en segundos, en un string.
			Si al cantidad de segundos es mayor a 60 los convierte en minutos, 
			si es mayor a 3600 los convierte en horas.
		Agumentos:
			t	-	Un numero que reprecente una cantidad de tiempo en segundos.
					Usualmente es el valor de retorno de la funcion time.time().
		Retorno:
			Devuelve un string con alguno de los siguientes formatos:
				"xx.yy seconds."
				"xx.yy minutes."
				"xx.yy hours."
	"""
	if t > 60.0:
		t = t/60.0
	else:
		return str(t)+" seconds."
	if t > 60.0:
		t = t/60.0
	else:
		return str(t)+" minutes."
	return str(t)+" hours."

def check_sync_type (system, sync_set):
	"""
		Descripcion:
			Esta funcion dice cual es el tipo de sincronizacion que hay entre 
			los subsistemas.
		Argumentos:
			system	-	Instancia de la clase Set() que contiene el conjunto de 
						subsistemas, cada uno de estos una instancia de la clase
						lts.LTS().
			sync_set -	Instancia de la clase Set() que contiene el conjunto de 
						eventos en los que se quiere sincronizar.
		Retorno:
			SYNC_EMPTY	 -	Si el tipo de sincronizacion es vacia.
			SYNC_PARTIAL -	Si el tipo de sincronizacion es parcial.
			SYNC_FULL	 -	Si el tipo de sincronizacion es total.
	"""
	# Dice si hay al menos dos subsistemas que tengan eventos en comun.
	some_share = False
	# Almacena la interseccion de los eventos observables de todos los 
	# subsistemas.
	inter_set = system[0].get_event_set('o')
	for subsystem in system:
		inter_set = inter_set.intersection(subsystem.get_event_set('o'))
		if not some_share:
			for s in system:
				if s != subsystem and share_events(subsystem, s):
					some_share = True
					break
	if not sync_set or (not inter_set and not some_share):
		return SYNC_EMPTY
	elif inter_set.issubset(sync_set):
		return SYNC_FULL
	else:
		return SYNC_PARTIAL

def show_nondiag_info (subsystem, diag_res, graphic):
	"""
		Descripcion:
			Esta es una funcion auxiliar de "_mode_0()" que se encarga de 
			mostrar por pantalla la informacion de un subsistema no
			diagnosticable.
		Arguemtnos:
			subsystem	-	Una instancia de la clase lts.LTS() de la cual se 
							va a mostrar la informacion.
			diag_res	-	Resultado del analisis de diagnosticabilidad de 
							"subsistem". Es el valor de retorno del metodo
							"subsistem.is_diagnosable()".
			graphic		-	Es un booleano que dice si se debe graficar el 
							subsistema y su twin-plant o no.
		Retorno:
			No tiene.
	"""
	subsystem_tp = subsystem.get_twinplant()
	map_dict = {subsystem:lts.to_file(subsystem)}
	map_dict[subsystem_tp] = lts.to_file(subsystem_tp)
	print "\nThe subsystem",subsystem.name,"has the following " + \
		  "non-diagnosable faults:"
	for (f,path) in diag_res['fault_list']:
		print "* Fault:", f
		print "- Undecidible path:"
		print "\t", path_to_string(path, subsystem_tp, map_dict)
		subsys_paths = tp_to_sys_path(path, subsystem, map_dict)
		print "- Undesirable pair:"
		print '\t', subsys_paths[0]
		print '\t', subsys_paths[1]
	if graphic:
		graph(subsystem.name)
		graph(subsystem_tp.name)

def share_events (s1, s2, sync_set=None):
	"""
		Descripcion:
			Esta funcion dice si dos sistemas comparten eventos de 
			sincronizacion. Si "sync_set" se espesifica dice si los sistemas 
			comparten eventos de sincronizacion que esten en "sync_set".
		Argumentos:
			s1,s2	-	Instancias de la clase lts.LTS().
			syn_set	-	Conjunto de eventos de sincronizacion. Instancia de la
						clase Set() cuyos elementos son cadenas de caracteres.
		Retorno:
			True	-	Si los sistemas tienen eventos de sincronizacion en
						comun / especificados en "sync_set".
			False	-	En caso contrario.
	"""
	e1 = s1.get_event_set('o')
	e2 = s2.get_event_set('o')
	result = False
	if sync_set:
		if e1.intersection(e2).intersection(sync_set):
			result = True
		else:
			result = False
	else:
		if e1.intersection(e2):
			result = True
		else:
			result = False
	return result

def share_all_events (s1, s2, sync_set):
	"""
		Descripcion:
			Esta funcion dice si dos sistemas comparten todos los eventos 
			de sincronizacion dados en "sync_set".
		Argumentos:
			s1, s2	 -	Instancias de la clase lts.LTS().
			sync_set -	Conjunto de eventos de sincronizacion.
		Retorno:
			True	 -	Si "s1" y "s2" comparten todos los eventos de
						"sync_set".
			False	 -	En caso contrario.
	"""
	e1 = s1.get_event_set('o')
	e2 = s2.get_event_set('o')
	if sync_set.intersection(e1) == sync_set.intersection(e2):
		return True
	else:
		return False

def stack_to_path (stack):
	"""
		Descripcion:
			Convierte una pila (una lista en realidad) con determinado formato 
			en un camino (una lista con otro formato).
		Argumentos:
			stack	-	Instancia de la clase List() cuyos elementos deben ser
						3-uplas con el siguiente formato: (s,e,l) donde "s" es
						una instancia de la clase State(), "e" es un evento 
						(cadena de caracteres) y "l" es una lista cuyo contenido
						es ignorado.
		Retorno:
			Una instancia de la clase List() que representa un camino.
			La lista tiene el siguiente formato: [s0,e0,s1,e1,...] donde los 
			elementos "s_i" son instancias de la clase State() y los elementos 
			"e_i" son eventos (cadena de caracteres).
	"""
	result = []
	for (s,e,l) in stack:
		if e != '':
			result.append(e)
		result.append(s)
	return result

def path_to_string (path, system, maps_dict=None):
	"""
		Descripcion:
			Convierte un camino (una lista con cierto formato) en una cadena de 
			caracteres.
		Argumentos:
			path		-	Una instancia de la clase List() que representa un 
							camino. La lista tiene el siguiente formato: 
							[s0,e0,s1,e1,...] donde los elementos "s_i" son 
							instancias de la clase State() y los elementos 
							"e_i" son eventos (cadena de caracteres).
			system		-	Una instancia de la clase lts.LTS() a la cual el 
							camino "path" pertenece.
			maps_dict	-	Opcional. Es un diccionario de diccionarios.
							Instancia de la clase Dict(), la cual tiene mapeos 
							de los nombres de los estados de "system". Los 
							datos de este diccionario deben ser los retornados 
							por la llamada a la funcion lts.to_file(), y las 
							claves deben ser los sistemas pasados como argumento
							a lts.to_file(). En la forma mas simple de uso de 
							esta funcion con maps_dict!=None este  tiene el 
							siguiente formato: {system:lts.to_file(system)}
		Retorno:
			Una cadena de caracteres que representa al camino "path".
			El formato de esta cadena es el siguiente:
				"(n1) -> e1 -> (n2) -> e2 -> (n3) ... [name]"
			donde los "n_i" son los nombres de los estados y los "e_i" son los 
			nombres de los eventos; "[name]" es el nombre del sistema al que 
			pertenece el camino.
	"""
	# Hago una copia del camino path.
	pathc = list(path)
	# Busco el mapeo para 'path'.
	if maps_dict:
		map_info = maps_dict[system]
	# Convierto a 'pathc' en un string
	for i in range(0,len(pathc)):
		if i%2 == 0:
			if maps_dict:
				state_name = map_info[str(pathc[i])]
			else:
				state_name = str(pathc[i])
			pathc[i] = '('+state_name+')'
		else:
			pathc[i] = '"'+str(pathc[i])+'"'
	separator = ' -> '
	return separator.join(pathc) + '\t[' + system.name + ']'

def tp_to_sys_path (path, system, maps_dict=None):
	"""
		Descripcion:
			Transforma un camino de una twinplant en la parte observable de dos
			caminos del sistema al que corresponde la twinplant.
		Argumentos:
			path		-	Una instancia de la clase List() que representa un 
							camino. La lista tiene el siguiente formato: 
							[s0,e0,s1,e1,...] donde los elementos "s_i" son 
							instancias de la clase State() y los elementos 
							"e_i" son eventos (cadena de caracteres).
			system		-	Una instancia de la clase lts.LTS() a la cual el 
							camino "path" pertenece.
			maps_dict	-	Opcional. Es un diccionario de diccionarios.
							Instancia de la clase Dict(), la cual tiene mapeos 
							de los nombres de los estados de "system". Los 
							datos de este diccionario deben ser los retornados 
							por la llamada a la funcion lts.to_file(), y las 
							claves deben ser los sistemas pasados como argumento
							a lts.to_file(). En la forma mas simple de uso de 
							esta funcion con maps_dict!=None este  tiene el 
							siguiente formato: {system:lts.to_file(system)}
		Retorno:
			Una una tupla de cadena de caracteres que representa a los camino 
			izquiero y derecho contenidos en "path".
			El formato de estas cadenas es el siguiente:
				"(n1) -> e1 -> (n2) -> e2 -> (n3) ... [name]"
			donde los "n_i" son los nombres de los estados y los "e_i" son los 
			nombres de los eventos; "[name]" es el nombre del sistema al que 
			pertenece el camino.
	"""
	# Hago copias de 'path'.
	pathL = list(path)
	pathR = list(path)
	# Busco el mapeo para 'system'.
	if maps_dict:
		map_info = maps_dict[system]
	# Convierto a 'path' en un string
	for i in range(0,len(path)):
		if i%2 == 0: # En los lugares pares estan los estados.
			if maps_dict:
				sL = (str(pathL[i].left_state).partition('#'))[0]
				sR = (str(pathR[i].right_state).partition('#'))[0]
				state_name_L = map_info[sL]
				state_name_R = map_info[sR]
			else:
				state_name_L = str(pathL[i].left_state)
				state_name_R = str(pathR[i].right_state)
			pathL[i] = '('+state_name_L+')'
			pathR[i] = '('+state_name_R+')'
		else: # En los lugares impares estan los eventos.
			pathL[i] = '"'+str(pathL[i])+'"'
			pathR[i] = '"'+str(pathR[i])+'"'
	separator = ' -> '
	r1 = separator.join(pathL)+'\t['+system.name+']'
	r2 = separator.join(pathR)+'\t['+system.name+']'
	return (r1,r2)

def graph (file):
	"""
		Descripcion:
			Esta funcion utiliza el programa 'ltsgraph' para graficar un LTS 
			almacenado en un archivo con formato 'Aldebaran'.
		Argumentos:
			file	-	Una cadena de caracteres con la ruta al archivo del 
						sistema que se quiere graficar.
		Retorno:
			No tiene.
	"""
	# Busco el ejecutable en el path.
	ltsgraph = ''
	if sys.platform == 'win32':
		for path in os.getenv('PATH').split(';'):
			if not path.endswith('\\'): path += '\\'
			path += "ltsgraph.exe"
			if os.path.isfile(path):
				ltsgraph = path
				break
	else:
		for path in os.getenv('PATH').split(':'):
			if not path.endswith('/'): path += '/'
			path += "ltsgraph"
			if os.path.isfile(path):
				ltsgraph = path
				break
	if not file.endswith('.aut'):
		file += '.aut'
	args = ["ltsgraph.exe", file]
	if ltsgraph != '':
		os.spawnv(os.P_NOWAIT, ltsgraph, args)
